深度解析 | Flutter web 支持的最新进展
我们将通过这篇文章深入分享将 Flutter 引入 web 平台的最新进展,重点介绍我们为与 web 平台进行深度、原生集成所做的努力。
我们对 Flutter 的愿景是提供一个可移植的工具包,让您可以在任何屏幕上尽情绘制像素,打造美好的体验。正如我们不久前和大家分享的一样,我们在框架和 API 的选择上将应用置于中心位置,构建出了一个可以跨移动、桌面和嵌入式设备的分层架构,且不影响性能和品质。
Web 正是这项工作的核心。Flutter 的诞生离不开 Chrome 团队的不懈探索。它的灵感源自 web 的生产力和迭代开发模型,我们的许多工程师在构建浏览器引擎和网络标准方面都有多年的经验。我们之所以将网络浏览器作为目标,是因为它是互联网的 "心脏": 计算机历史上最普遍、最灵活、最广泛的应用模型。
多年以来,web 平台和 HTML DOM 在本质上相差无几。然而最近,随着 web 平台不断扩展,API 和功能越来越丰富,使得开发者得以接触到底层操作系统和硬件。从硬件加速的图形绘制到 Shell 集成和 PWA,从 Service Worker 到新的布局和绘制 API,对于复杂的应用甚至是在此基础之上布局的框架而言,web 都是一个越来越强大的目标平台。
硬件加速的图形绘制 https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API Shell 集成 https://w3c.github.io/manifest/ PWA https://web.dev/progressive-web-apps/ Service Worker https://developers.google.cn/web/fundamentals/primers/service-workers 布局和绘制 API https://developer.mozilla.org/en-US/docs/Web/Houdini
Flutter 对 web 的支持不仅仅是对 DOM 的转译: 它充分利用这些全新的 API 集,既提供真正的 web 体验,又完整保留了 Flutter 的特色。
在这篇文章中,我们将详细介绍具体的实现过程,分享 Flutter 如何使用 web 来实现丰富的交互式体验,而无需重写现有 Flutter 代码。我们还会介绍无障碍功能和后端编译选择等内容,虽然初上手尝试 Flutter 的开发者们可能一时还用不到这些功能。
不只是 canvas 绘制
用于为 widget、动画和手势等常见的习惯用法提供抽象的框架。 使用公开的系统 API 在目标设备上进行渲染的引擎。
Flutter 的分层模型 https://flutter.cn/docs/resources/technical-overview#layer-cakes-are-delicious
框架代码是用 Dart 编写的。我们将其与您自己的代码相结合,并在编译时应用摇树优化 (tree-shaking) 算法,以便只将您的应用使用到的代码下载到浏览器中。Flutter 采用响应式模型,根据状态变化渲染用户界面。Flutter 在每帧内构建 widget,执行布局,然后使用底层浏览器 API 绘制界面变化。
使用摇树优化算法和延迟加载优化 Flutter web 应用性能 https://medium.com/flutter/optimizing-performance-in-flutter-web-apps-with-tree-shaking-and-deferred-loading-535fbe3cd674
△ Flutter 的 web 架构是多层系统,框架位于浏览器引擎之上
默认情况下,web 模式使用标准的 HTML DOM 和 Canvas 技术。在这种模式下,引擎将每个生成的 Flutter 场景转换为 HTML、CSS 或 Canvas,并以 HTML 元素树的形式将帧渲染到页面上。
我们将这种方法称为 DomCanvas 后端。它以简洁的代码为我们提供了跨浏览器的最大兼容性,非常适合会话短暂、需要快速启动的应用。
尽管我们在 DomCanvas 的性能和保真度方面持续取得良好进展,但我们还在并行开发基于 CanvasKit 的后端,该后端使用 WebAssembly 和 WebGL 在浏览器中渲染 Skia 绘制命令。我们之所以开始尝试使用 CanvasKit,是因为 Skia 与 Flutter 移动和桌面端使用的图形引擎相同,而且不同于 HTML DOM 的是,它可以直接访问底层图形堆栈,从而实现与原生 Flutter 完全对等的性能。
CanvasKit https://skia.org/user/modules/canvaskit WebAssembly https://webassembly.org/ WebGL https://www.khronos.org/webgl/
尽管 DomCanvas 的浏览器支持更广泛、代码更少、初始页面加载速度更快,但 CanvasKit 允许 Flutter 开发者采用新技术来构建图形密集型 web 应用。将来,其中一种方法的优势可能会变得势不可挡,但就目前而言,Flutter 可以让您选择最适合您所需用例的后端。
选择合适的渲染方案 https://github.com/flutter/flutter/wiki/Experimental-flags-for-web-support#canvaskit-backend
在浏览器中提供原生的体验
要让 web 应用在浏览器中提供原生的体验,它必须支持各种浏览器中的惯用操作,包括原生的文本交互和滚动行为、后退/前进导航以及无障碍功能,所有这些都要在各种各样的设备和环境中拥有良好的性能。
构建 Flutter 的 web 支持时,我们面临的最大挑战之一就是开发一种专门用于文本布局的布局系统。为了对文本段落进行布局,Flutter 会创建一个 paragraph 对象并调用其 layout() 方法。由于 web 目前缺少直接的文本布局 API,因此我们通过触发 layout() 并观测框架中其他元素布局属性值的变化,来实现通过 DOM 对文本段落进行各种测量的目的。
Paragraph https://api.flutter.cn/flutter/dart-ui/Paragraph-class.html layout() 方法 https://api.flutter.cn/flutter/dart-ui/Paragraph/layout.html
可以想象,这些测量的成本开销十分之大,因此我们最近开始尝试使用 canvas API 来测量文本以改进两种后端方法。我们发现这些 API 可以将文本布局性能提高多达 6 倍,并且还解决了多行文本和文本溢出的一些问题。您可以使用发布 (Release) 模式选项来启用这些 canvas 文本改进。(截至 2020 年 7 月 30 日更新: master 渠道中的 canvas 文本测量功能已经默认启用,并将逐步推广至其他渠道。)
使用 canvas 测量文本 https://github.com/flutter/flutter/issues/33523 Web 支持中的实验性选项: 文本渲染性能改进 https://github.com/flutter/flutter/wiki/Experimental-flags-for-web-support#text-rendering-performance-improvements
滚动
优化静态内容滚动 https://github.com/flutter/engine/pull/17621 性能基准 https://docs.google.com/spreadsheets/d/10AXb-4ulJipM6ZtiieFEBXhmybB6-_gTV6HDQlgcQdA/edit#gid=2047450737
由于 Flutter 框架最初是为原生移动应用设计的,因此 Flutter 网络应用已经对移动浏览器的手势和滚动物理效果提供了很好的支持。桌面浏览器的滚动行为支持取决于我们在框架级别为 Flutter 桌面端所做的工作。到目前为止,我们已经能够使用鼠标和其他鼠标/滚轮事件来处理拖拽滚动,但键盘滚动支持依然有待添加。
与原生应用不同,web 应用在浏览器中自带后退按钮。现在,浏览器后退按钮的行为与 Flutter 应用中的 Navigator.pop() 都相差无几。这意味着,当用户在浏览器中打开指向 Flutter web 应用的链接时,框架会将初始路径拆分成几个部分,并逐一加入导航堆栈中。
以 Flutter 目前的导航为例,您可以尝试从 google.com 跳转到 gallery.flutter.dev/#/demo/banner,然后点击浏览器的后退按钮。
△ 现在,浏览器的后退按钮会尝试匹配 Flutter 应用的后退按钮路由历史记录
您会发现,它不会按照预期带您回到 google.com,而是先返回网站的首页: gallery.flutter.dev。Flutter 会拆分初始 Gallery 应用的路径,将 gallery.flutter.dev 和 /demo/banner 页面都 push 进导航路径堆栈中,因此当浏览器后退按钮触发 Navigator.pop() 时,它会返回到 gallery.flutter.dev,因为应用内部的历史记录就是这样设置的。
我们一直在努力通过 Navigator 2.0 和新的 Router widget 改善 Flutter 在所有平台上的导航/路由功能。这不仅会使导航更具声明性和灵活性,还会改善路由历史记录在 Flutter web 应用中的作用方式。
Navigator 2.0
http://flutter.dev/go/navigator-with-router
Router widget
http://flutter.dev/go/router-and-widgetsapp-integration
实现无障碍功能
就设计而言,Flutter 通过构建独立于 RenderObject 树的 SemanticsNode 树来实现无障碍功能。Flutter 的无障碍功能系统目前是选择加入的,这意味着最终用户必须选择是否启用辅助技术。启用后,通过扫描渲染树以及合并表示屏幕上单个逻辑交互元素的语义节点来生成 SemanticsNode 树。
为了在 web 上实现此操作,我们又生成一个 DOM 树,它与作为 RenderObject 树的 DOM 树平行,并将标记、操作、标签和其他语义属性转换成 ARIA (Accessible Rich Internet Applications)。
每个 Flutter web 应用都可以实现无障碍功能。例如,当您启用屏幕阅读器辅助技术 (如适用于 iOS 和 macOS 的 VoiceOver),Flutter Gallery 应用就能提供无障碍体验。在按照屏幕阅读器的说明明确启用应用的无障碍功能后,您会看到应用生成了一个语义树。
无障碍功能 https://flutter.cn/docs/development/accessibility-and-localization/accessibility VoiceOver https://www.apple.com/accessibility/mac/vision/ Flutter Gallery 应用 https://gallery.flutter.cn/
Flutter web 的语义功能已经可以媲美当前 Flutter 语义系统的功能,但是我们需要添加自动化测试来确保不会出现回退。我们还需要支持一些复杂的功能,比如表格以及元素关系。
表格 https://github.com/flutter/flutter/issues/45205 元素关系 https://developers.google.cn/web/fundamentals/accessibility/semantics-aria/aria-labels-and-relationships
Rive: Flutter web 的典型示例
Rive 是一款典型的富交互应用,它展示了 Flutter 对 web 的支持能力。Rive 目前发布了其重新编写的设计协作工具,该工具完全用 Flutter 构建。
Rive 是一个动画设计应用,帮助设计师和开发者创建高质量且可以轻松集成到任何平台的动画素材。为了同时提供流畅的实时界面和动画,Rive 需要在现代浏览器和其他平台上进行大量的图形渲染以及确保高性能的体验。
Rive 2 beta 版完全使用 Flutter 重写,因此也得以用同一个代码库提供 web 和桌面版本。其 web 版使用 CanvasKit 后端和 Skia 在两个平台上提供一致的体验。敬请关注 Rive 即将发布的技术博客文章,了解他们如何使用 Flutter 重建工具。目前,您可以注册试用其 beta 版 web 应用,名额有限,赶快行动吧。
Rive 2 beta https://blog.rive.app/announcing-rive-2/ 了解 Rive 2 https://www.bilibili.com/video/BV1vp4y1q7cs/ 注册体验 https://beta.rive.app/
总结
希望这篇文章能让您深入了解我们是如何充分利用 web 平台的特性来打造 Flutter 的。就在几年前,Flutter web 在质量和性能上还谈不上差强人意,但是随着新的 web 技术的引入和平台的不断进步,我们终于能够更好地利用底层设备的潜力。我们将紧跟 web 平台的发展脚步不断前进,继续朝着稳定版本的方向努力,不断改进对文本交互、滚动、导航和无障碍功能的支持。
想要构建 Flutter web 应用?从入门指南开始试试吧。如果您已着手构建了一些应用,欢迎通过 CodePen 或网址和我们分享您的作品!
使用 Flutter 构建 web 应用 https://flutter.cn/docs/get-started/web CodePen https://codepen.io/pen/editor/flutter
推荐阅读